package com.aloha.springframework.beans.factory.xml;

import cn.hutool.core.util.StrUtil;
import com.aloha.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import com.aloha.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import com.aloha.springframework.beans.BeansException;
import com.aloha.springframework.beans.PropertyValue;
import com.aloha.springframework.beans.factory.config.BeanDefinition;
import com.aloha.springframework.beans.factory.config.BeanReference;
import com.aloha.springframework.beans.factory.support.AbstractBeanDefinitionReader;
import com.aloha.springframework.beans.factory.support.BeanDefinitionRegistry;
import com.aloha.springframework.core.io.Resource;
import com.aloha.springframework.core.io.ResourceLoader;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.List;

/**
 * @author DaiZhiHeng
 * @description 提供通过 XML 文件 加载BeanDefinition的能力
 * @date 2023/6/27 21:16
 */
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        super(registry);
    }

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
        super(registry, resourceLoader);
    }

    @Override
    public void loadBeanDefinitions(Resource resource) throws BeansException {
        try (InputStream inputStream = resource.getInputStream()) {
            doLoadBeanDefinitions(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
            throw new BeansException("在解析：" + resource + " 时出错啦！！！", e);
        }
    }

    @Override
    public void loadBeanDefinitions(Resource... resources) throws BeansException {
        for (Resource resource : resources) {
            loadBeanDefinitions(resource);
        }
    }

    @Override
    public void loadBeanDefinitions(String location) throws BeansException {
        loadBeanDefinitions(getResourceLoader().getResource(location));
    }

    @Override
    public void loadBeanDefinitions(String... locations) throws BeansException {
        for (String location : locations) {
            loadBeanDefinitions(location);
        }
    }

    /**
     * 加载XML文件的具体实现
     */
    protected void doLoadBeanDefinitions(InputStream inputStream) throws DocumentException {
        SAXReader reader = new SAXReader();
        Document document = reader.read(inputStream);

        Element root = document.getRootElement();

        // 解析context:component-scan标签并扫描指定包中的类，提取类信息，组装成BeanDefinition
        Element componentScan = root.element("component-scan");
        if (componentScan != null) {
            String scanPath = componentScan.attributeValue("base-package");
            if (StrUtil.isEmpty(scanPath)) {
                throw new BeansException("待扫描的包不能为空");
            }
            scanPackage(scanPath);
        }
        // 判断 aop:aspectj-autoproxy 是否存在，如果存在创建AspectJ自动代理的后置处理器
        Element autoProxy = root.element("aspectj-autoproxy");
        if (autoProxy != null) {
            getRegistry().registerBeanDefinition("com.aloha.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator", new BeanDefinition(AnnotationAwareAspectJAutoProxyCreator.class));
        }

        List<Element> beanList = root.elements("bean");
        for (Element bean : beanList) {
            String beanId = bean.attributeValue("id");
            String beanName = bean.attributeValue("name");
            String className = bean.attributeValue("class");
            String initMethodName = bean.attributeValue("init-method");
            String destroyMethodName = bean.attributeValue("destroy-method");
            String beanScope = bean.attributeValue("scope");
            String lazyInit = bean.attributeValue("lazyInit");
            Class<?> clazz;
            try {
                clazz = Class.forName(className);
            } catch (ClassNotFoundException e) {
                throw new BeansException("Cannot find class [" + className + "]");
            }
            //id优先于name
            beanName = StrUtil.isNotEmpty(beanId) ? beanId : beanName;
            if (StrUtil.isEmpty(beanName)) {
                //如果id和name都为空，将类名的第一个字母转为小写后作为bean的名称
                beanName = StrUtil.lowerFirst(clazz.getSimpleName());
            }

            if (getRegistry().containsBeanDefinition(beanName)) {
                //beanName不能重名
                throw new BeansException("Duplicate beanName[" + beanName + "] is not allowed");
            }
            BeanDefinition beanDefinition = new BeanDefinition(clazz);
            beanDefinition.setInitMethodName(initMethodName);
            beanDefinition.setDestroyMethodName(destroyMethodName);
            beanDefinition.setLazyInit(Boolean.parseBoolean(lazyInit));
            if (StrUtil.isNotEmpty(beanScope)) {
                beanDefinition.setScope(beanScope);
            }

            List<Element> propertyList = bean.elements("property");
            for (Element property : propertyList) {
                String propertyNameAttribute = property.attributeValue("name");
                String propertyValueAttribute = property.attributeValue("value");
                String propertyRefAttribute = property.attributeValue("ref");

                if (StrUtil.isEmpty(propertyNameAttribute)) {
                    throw new BeansException("属性名不能为空");
                }

                Object value = propertyValueAttribute;
                if (StrUtil.isNotEmpty(propertyRefAttribute)) {
                    value = new BeanReference(propertyRefAttribute);
                }
                PropertyValue propertyValue = new PropertyValue(propertyNameAttribute, value);
                beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
            }
            //注册BeanDefinition
            getRegistry().registerBeanDefinition(beanName, beanDefinition);
        }
    }

    /**
     * 扫描注解Component的类，提取信息，组装成BeanDefinition
     */
    private void scanPackage(String scanPath) {
        String[] basePackages = StrUtil.splitToArray(scanPath, ',');
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(getRegistry());
        scanner.doScan(basePackages);
    }
}
